#ifndef __CONNECTION_H
#define __CONNECTION_H

using namespace std;

#include "constants.h"
#include "exchange.h"

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

class Exchange;

class Connection
{
 public:

  // Constructors
  Connection();
  Connection(int, char*, char*, int);
  Connection(const Connection& );
  Connection(string);
  Connection(Exchange);
  Connection(int, int, int, int);
  ~Connection();

  // Data
  int ID;
  int peerID;
  unsigned int myIP;
  unsigned int peerIP;
  unsigned short port;
  int sockfd;
  int state;

  Exchange *list;
  int spot;
  int listsize;

  // Functions
  int Connect();
  void Close();
  void AddExchange(Exchange);
  int operator==(const Connection&);
  int operator!=(const Connection&);
  Connection& operator=(const Connection&);
};

Connection::Connection()
{
  ID = -1;
  peerID = -1;
  spot = 0;
  list = NULL;
  sockfd = -1;
}

Connection::Connection(int id, char* myip, char* peerip, int p)
{
  ID = id;
  peerID = -1;
  myIP = inet_addr(myip);
  peerIP = inet_addr(peerip);
  port = p;
  spot = 0;
  list = new Exchange[MAXEXCHANGES];
  listsize = MAXEXCHANGES;
  sockfd = -1;  
}

Connection::Connection(const Connection& c)
{
  ID = c.ID;
  peerID = c.peerID;
  myIP = c.myIP;
  peerIP = c.peerIP;
  port = c.port;
  spot = c.spot;
  list = new Exchange[c.listsize];
  memcpy(list, c.list, c.listsize*sizeof(Exchange));
  listsize = c.listsize;
  sockfd = c.sockfd;
}

Connection::Connection(string line)
{
  char* token, contents[MAXL];

  strcpy(contents, line.c_str());
  token = strtok(contents, " ");
  int pass = 0;
  char type;

  peerID = -1;

  while (token != NULL)
    {
      switch(pass)
	{
	case 0: 
	  ID = atoi(token);
	  break;
	case 2:
	  type = token[0];
	  break;
	case 3:
	  myIP = inet_addr(token);
	  break;
	case 4:
	  if (type == 'A')
	    port = atoi(token);
	  else
	    peerIP = inet_addr(token);
	  break;
	case 5:
	  if (type == 'A')
	    peerIP = inet_addr(token);
	  else
	    port = atoi(token);
	  break;
	}
      pass++;
      token = strtok(NULL, " ");
    }
  list = new Exchange[MAXEXCHANGES];
  listsize = MAXEXCHANGES;
  spot = 0;
  sockfd = -1;
}

Connection::Connection(int id, int myip, int peerip, int p)
{
  ID = id;
  peerID = -1;
  myIP = myip;
  peerIP = peerip;
  port = p;
  spot = 0;
  list = new Exchange[MAXEXCHANGES];
  listsize = MAXEXCHANGES;
  sockfd = -1;
}

Connection::~Connection()
{
  Close();
  delete[] list;
  list = NULL;
}

Connection& Connection::operator=(const Connection& c)
{
  if (this != &c)
    {
      ID = c.ID;
      peerID = c.peerID;
      myIP = c.myIP;
      peerIP = c.peerIP;
      port = c.port;
      spot = c.spot;
      list = new Exchange[c.listsize];
      memcpy(list, c.list, c.listsize*sizeof(Exchange));
      listsize = c.listsize;
    }
  return *this;
}

int Connection::Connect()
{
  struct sockaddr_in my_addr, peer_addr;
  int on, n, base = BASE;

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
      perror("socket");
      return FALSE;
    }

  memset(&my_addr, 0, sizeof(my_addr));
  my_addr.sin_family = AF_INET;
  if (ID>0)
    my_addr.sin_port = htons(base+peerID%INC);
  else
    my_addr.sin_port = 0;
  my_addr.sin_addr.s_addr = myIP;

  if(setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR,
		 (void *) &on, sizeof (on)) == 1)
    {
      perror("setsockopt");
      return FALSE;
    }

  while(bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1)
    {
      base = base + INC;
      if (ID>0)
	my_addr.sin_port = htons(base+peerID%INC);
      else
	my_addr.sin_port = 0;
      if (base > MAXPORTS)
	return FALSE;
    }

  memset(&peer_addr, 0, sizeof(peer_addr));
  peer_addr.sin_family = AF_INET;
  peer_addr.sin_port = htons(port);
  peer_addr.sin_addr.s_addr = peerIP;

  cout << "trying to connect from "<<inet_ntoa(my_addr.sin_addr);
  cout<<" to "<<inet_ntoa(peer_addr.sin_addr)<<" on port "<< port << endl;
  if (connect(sockfd, (struct sockaddr *)&peer_addr, sizeof(peer_addr)) == -1)
    {
      perror("Connect");
      return FALSE;
    }
  return sockfd;
}

void Connection::Close()
{
  if (sockfd >= 0)
    close(sockfd);
}

void Connection::AddExchange(Exchange e)
{
  if (spot == listsize -1)
    {
      Exchange* newlist = new Exchange[2*listsize], *old;
      memcpy(newlist, list, listsize*sizeof(Exchange));
      listsize = 2*listsize;
      delete[] list;
      list = newlist;
    }
  list[spot++] = e;
}

int Connection::operator==(const Connection& c)
{
  return (c.ID == ID);
}

int Connection::operator!=(const Connection& c)
{
  return (c.ID != ID);
}


#endif
